Child → Parent
Data
父组件如何接收数据?
子组件如何传数据?
“In React, the standard way to pass data from a child to a parent is by using callback functions. The parent component defines a function to handle the data, then passes it to the child via props. The child calls this function whenever it wants to send data back. For example:
xxxxxxxxxx101// Parent2function Parent() {3const handleData = (value) => console.log('Received:', value);4return <Child onSendData={handleData} />;5}67// Child8function Child({ onSendData }) {9return <button onClick={() => onSendData('hello parent')}>Send Data</button>;10}This pattern, often called lifting state up, keeps React’s unidirectional data flow predictable, while letting children communicate with parents. It’s simple, reusable, and works for most scenarios without adding extra state management libraries.”
✅ 面试加分点
Render outside component tree
Component scope/tree
为什么普通渲染不能满足需求?
React 提供什么解决方案?
“In React, when you need to render an element outside the parent component’s DOM hierarchy — for example, a modal, tooltip, or toast — you use React Portals. A portal allows a child component to be rendered into a DOM node that exists outside the parent component’s DOM tree, while still keeping the React tree intact for state and context. For example:
xxxxxxxxxx71import { createPortal } from 'react-dom';2function Modal({ children }) {3return createPortal(4<div className="modal">{children}</div>,5document.getElementById('modal-root') // outside the main app DOM6);7}This way, the modal can appear above other components without being constrained by parent styles, but it still behaves like a normal React component in terms of state, props, and context.”
✅ 面试加分点
Code splitting
Implement in React
Why
为什么需要 code splitting?
如何在 React 中实现?
React.lazy + Suspense“Code splitting allows a React app to load only the code that’s needed for the current view, reducing initial bundle size and improving performance. In React, we can implement it using React.lazy for component-level lazy loading, combined with Suspense to show a fallback while the component is loading. For example:
xxxxxxxxxx101import { Suspense, lazy } from 'react';2const Dashboard = lazy(() => import('./Dashboard'));34function App() {5return (6<Suspense fallback={<div>Loading...</div>}>7<Dashboard />8</Suspense>9);10}For route-level splitting, we can lazy-load page components in combination with React Router, so each route only loads the necessary bundle. Overall, code splitting improves performance, reduces initial load time, and makes large React apps more scalable.”
✅ 面试加分点
Global store
Best way
React app / project
小型应用或者少量状态怎么办?
useReducer 或 useState 即可中大型应用怎么办?
其他优化?
“The best way to add a global store in a React app depends on the size and complexity of the project. For small to medium apps, React Context combined with useReducer or useState is sufficient for managing global state like theme, user info, or UI preferences. For larger applications, I usually prefer Redux Toolkit or Zustand, which provide scalable and performant global state management. These tools offer features like centralized state, selectors for efficient re-rendering, and middleware support for side effects. The key is to choose a solution that balances simplicity, maintainability, and performance, ensuring only the components that need the state are re-rendered when it changes.”
✅ 面试加分点
SSR (Server-Side Rendering)
Basic implementation
React SSR
react-dom/server 的 API,比如 renderToString 或 renderToNodeStream能写一个最基础的 SSR React 示例
理解 SSR 的流程:
✅ Basic React SSR Example (Without Next.js)
Project Structure
xxxxxxxxxx31/server.js2/App.js3/index.html
1、React Component (App.js)
xxxxxxxxxx61// App.js2import React from "react";34export default function App() {5 return <h1>Hello from React SSR!</h1>;6}2、Server Entry (server.js)
This uses Node.js + Express + ReactDOMServer to render a React component to HTML on the server.
xxxxxxxxxx301// server.js2import express from "express";3import React from "react";4import ReactDOMServer from "react-dom/server";5import App from "./App.js";67const app = express();89app.get("*", (req, res) => {10 const html = ReactDOMServer.renderToString(<App />);1112 const fullPage = `13 <!DOCTYPE html>14 <html>15 <head>16 <title>React SSR Example</title>17 </head>18 <body>19 <div id="root">${html}</div>20 <script src="/client.js"></script>21 </body>22 </html>23 `;2425 res.send(fullPage);26});2728app.listen(3000, () => {29 console.log("SSR server running at http://localhost:3000");30});3、Client Hydration (client.js)
To make the server-rendered HTML interactive, you call hydrateRoot on the client:
xxxxxxxxxx61// client.js2import React from "react";3import { hydrateRoot } from "react-dom/client";4import App from "./App.js";56hydrateRoot(document.getElementById("root"), <App />);➡️ What this demonstrates to an interviewer
You understand SSR renders HTML on the server using renderToString.
You know that the client must hydrate the HTML to make React interactive.
You understand the difference between server entry and client entry.
You can explain the benefits:
Short version you can say in the interview:
“A basic SSR setup uses
ReactDOMServer.renderToString()on the server to generate HTML, sends that to the client, and then useshydrateRoot()to attach React to the existing HTML. This gives faster initial load and better SEO. Next.js automates this, but this is the simplest manual implementation.”
✅ 面试加分点
renderToString 或 renderToNodeStreamReact Fiber
Old reconciliation algorithm
How it differs
旧算法的缺点?
Fiber 的优势?
简要回答:
“React Fiber is the new reconciliation algorithm and rendering engine introduced in React 16. Its main goal is to make rendering more incremental, interruptible, and responsive. In the old stack reconciler, updates were synchronous and blocking — when a large component tree needed updating, the entire tree would render before the browser could handle user interactions, leading to potential UI jank. Fiber breaks rendering work into small units called fibers, which can be paused, aborted, or assigned different priority levels. This allows React to interrupt long rendering tasks, handle high-priority updates like user input first, and continue low-priority tasks later. In short, Fiber improves performance, responsiveness, and scheduling flexibility compared to the old synchronous algorithm.”
✅ 面试加分点
Rerender a component
Determine when
Function components only
哪些操作会触发函数组件 rerender?
useState / useReduceruseContext 的组件React 内部如何判断?
React.memo,React 会对 props 做 浅比较,跳过不必要渲染优化方法
React.memo 对函数组件做 memoizationuseCallback / useMemo 对传递的 props 函数或对象做 memoization
函数组件的 rerender 触发条件
State 变化
useState 或 useReducer 更新 state → 触发 rerenderProps 变化
Context 变化
useContext 的组件,如果 Context 的 value 改变,也会 rerender优化方法(面试可顺带提)
React.memo + useCallback / useMemo → 避免不必要 rerender简要回答:
“In React function components, a component rerenders whenever its state, props, or context changes. Specifically:
- State updates via
useStateoruseReducertrigger a rerender, and the component function executes again.- Props changes from the parent component also trigger a rerender — even if the value seems the same, a new object or function reference will cause rerender.
- Context changes — if the component consumes a context with
useContext, it rerenders when the context value changes. To optimize performance, we can use React.memo to memoize the component and useCallback or useMemo to memoize props or functions passed down, so React can skip unnecessary rerenders. In short, React rerenders function components based on state, props, or context changes, and memoization techniques can help prevent unneeded updates.”
✅ 面试加分点
Concurrent features
How do they help
为什么需要 concurrent features?
Concurrent features 提供了什么能力?
具体帮助表现在哪?
“Concurrent features in React, introduced in React 18, allow React to render components concurrently rather than blocking the main thread. These features enable interruptible rendering, task prioritization, and smoother user experiences. For example, React can pause rendering a low-priority update, handle user input immediately, and then resume rendering. React also provides Transitions, which help distinguish between urgent updates like typing and non-urgent updates like rendering a large list. Overall, concurrent features help keep the UI responsive, improve performance in large applications, and allow React to handle multiple tasks efficiently without blocking user interactions.”
✅ 面试加分点
Batching behavior
What changed in React 18
什么是 batching?
setState,React 会把更新合并,只触发一次渲染React 17 及以前的限制?
React 18 新特性?
优势?
“Batching in React is the process of merging multiple state updates into a single render to improve performance. In React 17 and earlier, batching only worked inside React event handlers. Updates in setTimeout, promises, or native event handlers would trigger separate renders. In React 18, automatic batching was introduced, which extends batching to all updates, including asynchronous callbacks. Now multiple state updates inside promises, setTimeout, fetch, or any async operations are combined into a single render, reducing unnecessary renders and improving performance. In short, React 18 makes batching more consistent and efficient, helping applications render faster and maintain smoother UI updates.”
✅ 面试加分点
useMemo
useCallback
Difference
When you shouldn’t use them
useMemo 什么时候用?
useCallback 什么时候用?
什么时候不该用?
“
useMemoanduseCallbackare both React hooks used for memoization, but they serve different purposes.
useMemocaches the result of a function, helping to avoid expensive recalculations. For example, computing a sorted large array or complex derived value.useCallbackcaches the function itself, preventing it from being recreated on every render. This is useful when passing functions as props to child components wrapped inReact.memoto avoid unnecessary rerenders. You shouldn’t use them for every function or value. For cheap calculations or simple functions, the overhead of memoization may outweigh its benefits. Overusing these hooks can increase memory usage and code complexity. In short, useMemo → cache value, useCallback → cache function, and only use them when they provide a performance benefit.”
✅ 面试加分点
Suspense
How does it work?
Real use cases beyond lazy loading
面试官要看你是否知道 Suspense 的更高阶用途,而不仅仅用于 React.lazy
比如:
Suspense 是怎么工作的?(机制)
为什么需要 Suspense?(好处)
除了 lazy loading,还有什么实际场景?
简要回答:
“Suspense is React’s mechanism for handling asynchronous rendering. It works by letting components throw a Promise when they’re not ready yet, and a surrounding Suspense boundary catches it and shows a fallback UI until the data or resource is ready. This makes async flows much smoother compared to manually managing loading states in every component. Beyond lazy loading, Suspense has several real use cases: • Data fetching with libraries like Relay, React Query, or SWR that support Suspense-friendly APIs. • Image loading, where the UI waits for an image resource before rendering it. • Server Components and streaming SSR in React 18, where Suspense boundaries allow the server to progressively stream UI chunks. • Prefetching UI, letting React prepare parts of the UI in the background before the user navigates to them. Overall, Suspense is a general async boundary pattern that improves both developer experience and user-perceived performance.”
useImperativeHandleforwardRefrefforwardRefforwardRef?
useImperativeHandlelets a child control what is exposed to a parent through a ref. It is used together withforwardRef. Instead of exposing the DOM node, you expose specific methods. This is useful for actions like focus, scroll, or reset. It should be used rarely.
Example use case: expose a focus method
xxxxxxxxxx111const Input = forwardRef((props, ref) => {2 const inputRef = useRef(null);34 useImperativeHandle(ref, () => ({5 focus() {6 inputRef.current.focus();7 }8 }));910 return <input ref={inputRef} />;11});When to use it:
When not to use it:
Rule: Prefer props. Use useImperativeHandle only when necessary.
Large lists are slow because React renders too many DOM elements. The best solution is list virtualization. This means only rendering visible items. Libraries like
react-windoworreact-virtualizedhelp with this. You should also avoid unnecessary re-renders.
1. Use virtualization
xxxxxxxxxx91import { FixedSizeList } from "react-window";23<FixedSizeList4 height={400}5 itemCount={items.length}6 itemSize={35}7>8 {Row}9</FixedSizeList>2. Memoize list items
xxxxxxxxxx31const Row = React.memo(({ item }) => {2 return <div>{item.name}</div>;3});3. Use stable keys
4. Avoid heavy logic in render
useMemo if neededRule of thumb: Render less, re-render less, and keep each item simple.
useRefuseStateuseState triggers re-rendersuseRef does not trigger re-rendersuseRef keeps the same object between renders
useStateis used for data that affects the UI. When state changes, the component re-renders.useRefis used to store mutable values. Changing a ref does not cause a re-render. Refs are often used for DOM access or timers.
Use useState when:
Use useRef when:
xxxxxxxxxx21const countRef = useRef(0);2const [count, setCount] = useState(0);Rule:
If the UI depends on it → useState
If not → useRef
In SSR, React renders HTML on the server first. On the client, React hydrates the page by attaching event listeners. It reuses the existing HTML instead of creating new DOM nodes. A problem happens when server and client HTML are different. This is called a hydration mismatch.
Common problems:
window or document during server renderMath.random() or Date.now()Solutions:
useEffectxxxxxxxxxx31useEffect(() => {2 setMounted(true);3}, []);Rule of thumb: Server and client must render the same HTML during the first render.
useEffectA memory leak happens when a
useEffectkeeps something alive after the component unmounts. This usually happens when there is no cleanup function. For example, event listeners, timers, or subscriptions are not removed. Async tasks likefetchmay also update state after unmount. React warns about this in development mode.
Common fixes:
useEffectclearTimeout or clearIntervalExample:
xxxxxxxxxx91useEffect(() => {2 const id = setInterval(() => {3 console.log("running");4 }, 1000);56 return () => {7 clearInterval(id);8 };9}, []);This prevents the effect from running after the component is unmounted.
useEffectsetState after unmount a problem?API calls can cause memory leaks when they finish after the component unmounts. If the promise resolves and calls
setState, React warns about a memory leak. This happens because the async callback is still in memory. The component is gone, but the request is still running. Without cleanup, React cannot release the resources.
Main solutions:
Cancel the request
AbortController for fetchIgnore the result after unmount
mounted flagExample with AbortController:
xxxxxxxxxx161useEffect(() => {2 const controller = new AbortController();34 fetch("/api/data", { signal: controller.signal })5 .then(res => res.json())6 .then(data => setData(data))7 .catch(err => {8 if (err.name !== "AbortError") {9 console.error(err);10 }11 });1213 return () => {14 controller.abort();15 };16}, []);This ensures the API call does not update state after the component is unmounted.
useEffectaddEventListenerremoveEventListenerEvent listeners can cause memory leaks when they are added but never removed. The listener still exists after the component unmounts. It keeps a reference to the component’s callback. Because of this, the component cannot be garbage collected. Re-renders may also add the same listener multiple times.
Fix: always clean up listeners
xxxxxxxxxx111useEffect(() => {2 function handleResize() {3 console.log(window.innerWidth);4 }56 window.addEventListener("resize", handleResize);78 return () => {9 window.removeEventListener("resize", handleResize);10 };11}, []);Best practices:
useEffectThis prevents memory leaks caused by event listeners.
setIntervalsetTimeoutuseEffectsetInterval is more dangerous than setTimeoutYes,
setIntervalandsetTimeoutcan cause memory leaks in React. If they are not cleared, they keep running after the component unmounts. The timer callback keeps references to the component’s state and props. This prevents the component from being garbage collected.setIntervalis worse because it runs repeatedly.
Always clear timers in cleanup
xxxxxxxxxx91useEffect(() => {2 const id = setInterval(() => {3 console.log("running");4 }, 1000);56 return () => {7 clearInterval(id);8 };9}, []);Best practices:
clearInterval for setIntervalclearTimeout for setTimeoutThis prevents memory leaks caused by timers.
useRefuseRef holds mutable values that survive re-rendersYes, closures and refs can cause memory leaks in React. A closure can keep old state or props in memory. If the closure is used in a long-running effect, it never gets released.
useRefstores values that stay for the lifetime of the component. If a ref holds large objects or DOM nodes, memory can grow.
Closures:
xxxxxxxxxx71useEffect(() => {2 const id = setInterval(() => {3 setCount(c => c + 1);4 }, 1000);56 return () => clearInterval(id);7}, []);Refs:
xxxxxxxxxx51useEffect(() => {2 return () => {3 myRef.current = null;4 };5}, []);Rule of thumb: If something lives longer than the component, it can cause a memory leak.